﻿Imports System.Collections.Concurrent
Imports Runa.Foundation.Messaging
Imports Runa.Common.Helper
Imports Runa.Foundation.Threading
Imports Runa.UI.Controls
Imports System.Reflection
Imports System.Runtime.InteropServices
Imports Microsoft.Xna.Framework
Imports Runa.Core.Services

Namespace Global.Runa.UI.Threading

    Public NotInheritable Class Dispatcher
        Implements IMessageQueue
        Implements IMessageFilter

        Public Event ShutdownFinished As EventHandler
        Public Event ShutdownStarted As EventHandler
        Public Event UnhandledException As DispatcherUnhandledExceptionEventHandler
        Public Event UnhandledExceptionFilter As DispatcherUnhandledExceptionFilterEventHandler

        Private Const PROCESS_NONE = 0
        Private Const PROCESS_BACKGROUND = 1
        Private Const PROCESS_FOREGROUND = 2

        Private Const TIMERID_BACKGROUND = 1
        Private Const TIMERID_TIMERS = 2
        Private Const DELTA_BACKGROUND = 1

        Friend Delegate Sub ShutdownCallback()

        Protected Friend Shared Property Dispachers() As New ConcurrentDictionary(Of Integer, Dispatcher)

        Public Property ThreadID() As Integer

        Protected Friend Property Thread() As Thread
        Protected Friend ReadOnly Property MessageQueue As ConcurrentQueue(Of Message) Implements Foundation.Messaging.IMessageQueue.MessageQueue
            Get
                Return CType(Me.Thread, IMessageQueue).MessageQueue
            End Get
        End Property

        Private ReadOnly Property HasUnhandledExceptionHandler() As Boolean
            Get
#If NETFX_CORE Or WINDOWS_PHONE Then
                Return True
#Else
                Return HasHandler(Me, "UnhandledExceptionHandler")
#End If
            End Get
        End Property

        Private ReadOnly Property IsInputPending() As Boolean
            Get
                Return SafeNativeMethods.GetQueueStatus(QS_ALLINPUT) > 0
            End Get
        End Property

        Friend Shared ReadOnly Property SafeNativeMethods() As ISafeNativeMethods
            Get
                Return Dispatcher._game.Services.GetService(GetType(ISafeNativeMethods))
            End Get
        End Property

        Private _queue As New PriorityQueue(Of DispatcherOperation)
        Private _timers As New List(Of DispatcherTimer)
        Private _timersVersion As Long
        Private _frameDepth As Integer
        Private _postedProcessingType As Integer
        Private _hasShutdownFinished As Boolean
        Private _dueTimeFound As Boolean
        Private _dueTimeInTicks As Integer
        Private _isWin32TimerSet As Boolean
        Private _unhandledExceptionEventArgs As DispatcherUnhandledExceptionEventArgs
        Private _exceptionFilterEventArgs As DispatcherUnhandledExceptionFilterEventArgs

        Private Shared _globalLock As Object
        Private Shared _exceptionWrapper As ExceptionWrapper
        Private Shared _foregroundPriorityRange As PriorityRange
        Private Shared _backgroundPriorityRange As PriorityRange
        Private Shared _idlePriorityRange As PriorityRange
        Private Shared _game As Game
        Private Shared _msgProcessQueue As MessageType
        Private Shared ReadOnly ExceptionDataKey As Object

        Friend _instanceLock As New Object
        Friend _exitAllFrames As Boolean
        Friend _disableProcessingCount As Integer
        Friend _hasShutdownStarted As Boolean

        Public Shared ReadOnly Property CurrentDispatcher() As Dispatcher
            Get
                Dim currentTid = GetCurrentThreadId()
                If Dispachers.ContainsKey(currentTid) = False Then
                    While Dispachers.TryAdd(currentTid, New Dispatcher(currentTid)) = False
                    End While
                End If
                Return Dispachers(currentTid)
            End Get
        End Property

        Shared Sub New()
            Dispatcher._foregroundPriorityRange = New PriorityRange(DispatcherPriority.Loaded, True, DispatcherPriority.Send, True)
            Dispatcher._backgroundPriorityRange = New PriorityRange(DispatcherPriority.Background, True, DispatcherPriority.Input, True)
            Dispatcher._idlePriorityRange = New PriorityRange(DispatcherPriority.SystemIdle, True, DispatcherPriority.ContextIdle, True)
            Dispatcher.ExceptionDataKey = New Object()
            Dispatcher._globalLock = New Object()
            Dispatcher.Dispachers = New ConcurrentDictionary(Of Integer, Dispatcher)
            Dispatcher._exceptionWrapper = New ExceptionWrapper()
            Dispatcher._msgProcessQueue = MessageType.WM_USER
            AddHandler Dispatcher._exceptionWrapper.Catch, AddressOf Dispatcher.CatchExceptionStatic
            AddHandler Dispatcher._exceptionWrapper.Filter, AddressOf Dispatcher.ExceptionFilterStatic
        End Sub

        Private Sub New()
            Me.ThreadID = GetCurrentThreadId()
        End Sub

        Friend Sub New(ByVal g As Game)
            Me.ThreadID = GetCurrentThreadId()
            Me.Thread = Nothing
            InitializeNativeLayer(g)
        End Sub

        Private Sub New(ByVal tid As Integer)
            Me.ThreadID = tid
            Me.Thread = ThreadPool.FromThread(tid)
        End Sub

        Friend Shared Function Create() As Dispatcher
            Dim result As New Dispatcher()
            If Dispachers.ContainsKey(result.ThreadID) Then
                Throw New Exception("There has been a dispacher for this thread.")
            Else
                result.Thread = Nothing
                Return result
            End If
        End Function

        Public Shared Sub Run()
            Dispatcher.CurrentDispatcher.PushFrame(New DispatcherFrame())
        End Sub

        Public Shared Function FromThread(ByVal tid As Integer) As Dispatcher
            Return Dispachers(tid)
        End Function

        Public Sub VerifyAccess()
            If Not Me.CheckAccess() Then
                Throw New InvalidOperationException("VerifyAccess")
            End If
        End Sub

        Public Function CheckAccess() As Boolean
            Return Me.ThreadID = GetCurrentThreadId()
        End Function

        Public Function Invoke(ByVal priority As DispatcherPriority, ByVal timeout As TimeSpan, ByVal method As [Delegate], ByVal arg As Object, ByVal ParamArray args As Object()) As Object
            Dim arglist As Object() = Nothing
            If args.Length > 1 Then
                arglist = New Object() {arg}.Concat(args)
            Else
                arglist = New Object() {arg}
            End If
            If Dispatcher._foregroundPriorityRange.Contains(priority) Then
                Return method.DynamicInvoke(arglist)
            Else
                If priority = DispatcherPriority.Inactive Then
                    Throw New ArgumentException("InvalidPriority", "priority")
                Else
                    Dim operation = Me.BeginInvoke(priority, method, arglist)
                    Dim state = operation.Wait(timeout)
                    Return operation.Result
                End If
            End If
        End Function

        Public Function BeginInvoke(ByVal operation As [Delegate], ByVal ParamArray prams() As Object) As DispatcherOperation
            Return Me.BeginInvoke(DispatcherPriority.Normal, operation, prams)
        End Function

        Public Function BeginInvoke(ByVal priority As DispatcherPriority, ByVal method As [Delegate], ByVal ParamArray prams() As Object) As DispatcherOperation
            Dim arglist = prams
            Dim operation As New DispatcherOperation(Me, method, priority, arglist, arglist.Length)
            Me._queue.Enqueue(priority, operation)
            SafeNativeMethods.SendMessage(Me.ThreadID, Message.Create(Me, Dispatcher._msgProcessQueue, Nothing, operation))
            Return operation
        End Function

        Friend Function Abort(ByVal operation As DispatcherOperation) As Boolean
            Try
                Me._queue.RemoveItem(New PriorityItem(Of DispatcherOperation)(operation))
                Return True
            Catch ex As Exception
                Return False
            End Try
        End Function

        Friend Sub AddTimer(ByVal timer As DispatcherTimer)
            SyncLock Me._instanceLock
                If Not Me._hasShutdownFinished Then
                    Me._timers.Add(timer)
                    Me._timersVersion = Me._timersVersion + 1
                End If
            End SyncLock
            Me.UpdateWin32Timer()
        End Sub

        Friend Sub RemoveTimer(ByVal timer As DispatcherTimer)
            SyncLock Me._instanceLock
                If Not Me._hasShutdownFinished Then
                    Me._timers.Remove(timer)
                    Me._timersVersion = Me._timersVersion + 1
                End If
            End SyncLock
            Me.UpdateWin32Timer()
        End Sub

        Friend Sub PromoteTimers(ByVal currentTimeInTicks As Integer)
            For Each item In Me._timers
                If item._dueTimeInTicks - Me._dueTimeInTicks < 0 Then
                    Me._dueTimeInTicks = item._dueTimeInTicks
                End If
            Next
        End Sub

        Public Shared Sub InitializeNativeLayer(ByVal g As Game)
            If Dispatcher._game Is Nothing Then
                Dispatcher._game = g
                SafeNativeMethods.RegisterWindowMessage("ProcessQueue", Dispatcher._msgProcessQueue)
            End If
        End Sub

        Public Shared Sub ValidatePriority(ByVal priority As DispatcherPriority, ByVal parameterName As String)
            If ((Not Dispatcher._foregroundPriorityRange.Contains(priority) AndAlso Not Dispatcher._backgroundPriorityRange.Contains(priority)) AndAlso (Not Dispatcher._idlePriorityRange.Contains(priority) AndAlso (priority <> DispatcherPriority.Inactive))) Then
#If WINDOWS_PHONE Or NETFX_CORE Then
                Throw New ArgumentException(parameterName)
#Else
                Throw New InvalidEnumArgumentException(parameterName, CInt(priority), GetType(DispatcherPriority))
#End If
            End If
        End Sub

        Friend Sub UpdateWin32Timer()
            If Me.CheckAccess Then
                Me.UpdateWin32TimerFromDispatcherThread(Nothing)
            Else
                Me.BeginInvoke(DispatcherPriority.Send, New DispatcherOperationCallback(AddressOf Me.UpdateWin32TimerFromDispatcherThread), Nothing)
            End If
        End Sub

        Private Function UpdateWin32TimerFromDispatcherThread(ByVal unused As Object) As Object
            SyncLock Me._instanceLock
                If Not Me._hasShutdownFinished Then
                    Dim flag As Boolean = Me._dueTimeFound
                    Dim num2 As Integer = Me._dueTimeInTicks
                    Me._dueTimeFound = False
                    Me._dueTimeInTicks = 0
                    If (Me._timers.Count > 0) Then
                        Dim i As Integer
                        For i = 0 To Me._timers.Count - 1
                            Dim timer As DispatcherTimer = Me._timers.Item(i)
                            If (Not Me._dueTimeFound OrElse ((timer._dueTimeInTicks - Me._dueTimeInTicks) < 0)) Then
                                Me._dueTimeFound = True
                                Me._dueTimeInTicks = timer._dueTimeInTicks
                            End If
                        Next i
                    End If
                    If Me._dueTimeFound Then
                        If (Not Me._isWin32TimerSet OrElse Not flag) OrElse (num2 <> Me._dueTimeInTicks) Then
                            Me.SetWin32Timer(Me._dueTimeInTicks)
                        End If
                    ElseIf flag Then
                        Me.KillWin32Timer()
                    End If
                End If
            End SyncLock
            Return Nothing
        End Function

        Private Sub SetWin32Timer(ByVal dueTimeInTicks As Integer)
            Dim uElapse As Integer = (dueTimeInTicks - Environment.TickCount)
            If (uElapse < 1) Then
                uElapse = 1
            End If
            SafeNativeMethods.SetTimer(Me.ThreadID, TIMERID_TIMERS, uElapse)
            Me._isWin32TimerSet = True
        End Sub

        Private Sub KillWin32Timer()
            SafeNativeMethods.KillTimer(Me.ThreadID, TIMERID_TIMERS)
            Me._isWin32TimerSet = False
        End Sub

        Private Function CatchException(ByVal e As Exception) As Boolean
            Dim handled As Boolean = False
            Dim flag As Boolean = False
            Try
                RaiseEvent UnhandledException(Me, Me._unhandledExceptionEventArgs)
                handled = Me._unhandledExceptionEventArgs.Handled
                flag = True
            Finally
                If Not flag Then
                    handled = False
                End If
            End Try
            Return handled
        End Function

        Private Function ExceptionFilter(ByVal e As Exception) As Boolean
            If Not e.Data.Contains(Dispatcher.ExceptionDataKey) Then
                e.Data.Add(Dispatcher.ExceptionDataKey, Nothing)
            Else
                Return False
            End If
            Dim hasUnhandledExceptionHandler As Boolean = Me.HasUnhandledExceptionHandler
            If hasUnhandledExceptionHandler Then
                Me._exceptionFilterEventArgs.Initialize(e, hasUnhandledExceptionHandler)
                Dim flag2 As Boolean = False
                Try
                    RaiseEvent UnhandledExceptionFilter(Me, Me._exceptionFilterEventArgs)
                    flag2 = True
                Finally
                    If flag2 Then
                        hasUnhandledExceptionHandler = Me._exceptionFilterEventArgs.RequestCatch
                    End If
                End Try
            End If
            Return hasUnhandledExceptionHandler
        End Function

        Public Sub PushFrame(ByVal frame As DispatcherFrame)
            Me.SetWin32Timer(Environment.TickCount)
        End Sub

        Private Function RequestProcessing(Optional ByVal force As Boolean = False) As Boolean
            Dim success As Boolean = True
            Dim priority = _queue.MaxPriority
            Select Case priority
                Case DispatcherPriority.Invalid, DispatcherPriority.Inactive
                    Return success
                Case Else
                    If force Then
                        If Me._postedProcessingType = PROCESS_BACKGROUND Then
                            SafeNativeMethods.KillTimer(Me.Thread.ThreadID, TIMERID_BACKGROUND)
                        ElseIf Me._postedProcessingType = PROCESS_FOREGROUND Then
                            Dim msg As New Message
                            SafeNativeMethods.PeekMessage(msg, Me.Thread.ThreadID, Dispatcher._msgProcessQueue, Dispatcher._msgProcessQueue, 1)
                        End If
                        Me._postedProcessingType = 0
                    End If
                    If Dispatcher._foregroundPriorityRange.Contains(priority) Then
                        Return Me.RequestForegroundProcessing()
                    End If
                    Return Me.RequestBackgroundProcessing
            End Select
            Return success
        End Function

        Private Function RequestForegroundProcessing() As Boolean
            If (Me._postedProcessingType >= PROCESS_FOREGROUND) Then
                Return True
            End If
            If (Me._postedProcessingType = PROCESS_BACKGROUND) Then
                SafeNativeMethods.KillTimer(Me.ThreadID, TIMERID_BACKGROUND)
            End If
            Me._postedProcessingType = 2
            Return SafeNativeMethods.PostMessage(Message.Create(Nothing, Dispatcher._msgProcessQueue, 0, 0))
        End Function

        Private Function RequestBackgroundProcessing() As Boolean
            Dim flag As Boolean = True
            If (Me._postedProcessingType >= 1) Then
                Return flag
            End If
            If Me.IsInputPending Then
                Me._postedProcessingType = PROCESS_BACKGROUND
                SafeNativeMethods.SetTimer(Nothing, TIMERID_BACKGROUND, 1)
                Return True
            End If
            Return Me.RequestForegroundProcessing
        End Function

        Private Sub ProcessQueue()
            Dim invalid As DispatcherPriority = DispatcherPriority.Invalid
            Dim operation As DispatcherOperation = Nothing
            SyncLock Me._instanceLock
                Me._postedProcessingType = 0
                Dim flag2 As Boolean = Not Me.IsInputPending
                invalid = Me._queue.MaxPriority
                If (((invalid <> DispatcherPriority.Invalid) AndAlso (invalid <> DispatcherPriority.Inactive)) AndAlso (Dispatcher._foregroundPriorityRange.Contains(invalid) OrElse flag2)) Then
                    operation = Me._queue.Dequeue
                Else
                    invalid = Me._queue.MaxPriority
                    ' If there are some urgent messages or this is a quite low level priority operation.
                    Me.RequestProcessing()
                End If
            End SyncLock
            If operation IsNot Nothing Then
                Dim flag As Boolean = False
                operation.Invoke()
                If flag Then
                    If Dispatcher._idlePriorityRange.Contains(invalid) Then
                    End If
                End If
                operation.InvokeCompletions()
            End If
        End Sub

        Private Shared Sub CatchExceptionStatic(ByVal source As Object, ByVal e As ExceptionEventArgs)
            Dim dispatcher As Dispatcher = DirectCast(source, Dispatcher)
            e.Handled = dispatcher.CatchException(e.Exception)
        End Sub

        Private Shared Sub ExceptionFilterStatic(ByVal source As Object, ByVal e As ExceptionEventArgs)
            Dim dispatcher As Dispatcher = DirectCast(source, Dispatcher)
            e.Handled = dispatcher.ExceptionFilter(e.Exception)
        End Sub

        Private Function DispachMessage(ByRef m As Foundation.Messaging.Message) As Boolean Implements Foundation.Messaging.IMessageFilter.DispachMessage
            Return True
        End Function

        Private Function PreFilterMessage(ByRef m As Foundation.Messaging.Message) As Boolean Implements Foundation.Messaging.IMessageFilter.PreFilterMessage
            Return True
        End Function

        Private Function TranslateMessage(ByRef m As Foundation.Messaging.Message) As Boolean Implements Foundation.Messaging.IMessageFilter.TranslateMessage
            Return True
        End Function

        Private Function WndProc(ByRef m As Foundation.Messaging.Message) As Boolean Implements Foundation.Messaging.IMessageFilter.WndProc
            Dim message As MessageType = m.Message
            ' Debug.Assert(m.Message <> MessageType.WM_MOUSEMOVE)
            ' Debug.Assert(m.Message <> MessageType.WM_LBUTTONDOWN)
            If Me._disableProcessingCount > 0 Then
                Throw New InvalidOperationException("DispatcherProcessingDisabledButStillPumping")
            End If
            If message = Dispatcher._msgProcessQueue Then
                Me.ProcessQueue()
            ElseIf message = MessageType.WM_TIMER AndAlso CInt(m.WParam) = TIMERID_BACKGROUND Then
                SafeNativeMethods.KillTimer(m.HWnd, TIMERID_BACKGROUND)
                Me.ProcessQueue()
            ElseIf message = MessageType.WM_TIMER AndAlso CInt(m.WParam) = TIMERID_TIMERS Then
                Me.KillWin32Timer()
                Me.PromoteTimers(Environment.TickCount)
            Else
                Me.BeginInvoke(DispatcherPriority.Send,
                    Sub(msg As Message)
                        msg.HWnd = New Object()
                        SafeNativeMethods.TranslateMessage(msg)
                        SafeNativeMethods.DispachMessage(msg)
                    End Sub, m)
                'm.HWnd = New Object()
                'SafeNativeMethods.TranslateMessage(m)
                'SafeNativeMethods.DispachMessage(m)
            End If
            Return True
        End Function

    End Class

End Namespace